package client;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Observable;

import communication.ComSrvCli;


/**
 * Classe implementant un client dans un modele client-serveur.
 * 
 * La communication se base sur l'idee que seuls des objets sont transmis entre 
 * le client et le serveur.
 * lors de la reception d'un objet, celui-ci est transmis aux differents
 * observateurs de cette classe.
 * @author Julien Roch
 * @version 1.0
 */
public class NetClient extends Observable implements Runnable{

	private Thread threadClient;	// thread pour l'ecoute sur le socket
	private Socket socket;			// socket pour la connection des clients
	private ObjectOutputStream out;	// stream pour l'ecriture des objets a envoyer
	private ObjectInputStream in;	// stream pour la lecture des objets sur le socket
	private LostConnectionInterface lostConn; // gerer les exceptions
	private boolean isConnected = false;
	
	/**
	 * Constructeur vide
	 */
	public NetClient(){}
	/**
	 * Affecte un gestionnaire d'exception au netCLient
	 * @param lost
	 */
	public NetClient(LostConnectionInterface lost){
		this.lostConn = lost;
	}
	/**
	 * Methode d'ecoute du serveur
	 */
	public void run(){
		try {
			in = new ObjectInputStream(socket.getInputStream());
		} catch (IOException e) {	// erreur d'E/S sur le socket
			if(this.lostConn !=null)
				this.lostConn.lostConnection(e);
		}
		while(true){
			try {
				// on gere l'objet au moyen du handler
				Object o = in.readObject();			// lecture de l'objet
				if(ComSrvCli.class.isInstance(o))
					((ComSrvCli)o).setSocket(socket);	// on enregistre le socket
				
				// notification (thread-safe) des observateurs
				synchronized (this) {
					setChanged();
					this.notifyObservers(o);
				}
			} catch(EOFException ex){	// exception de fin de fichier. Se produit lors de la fermeture du socket
				System.out.println("Connection closed by server");
				try {
					socket.close();
					return;
				} catch (IOException exceptionFermeture) {	// Exception lors de la fermeture du socket
					exceptionFermeture.printStackTrace(); // pour DEBUG
					return;
				}
			} catch (Exception e) {	// autres exceptions
				e.printStackTrace(); /** @debug **/
				return;
			}
		}
	}
	
	/**
	 * Connection au serveur
	 * @param server l'adresse du serveur
	 * @param port le port sur lequel se connecter
	 * @return si la connection est faite sans erreurs
	 */
	public boolean connect(InetAddress server, int port){
		
		// creation du socket
		try {
	
			
			this.socket = new Socket(server, port);
			this.isConnected = true;
			
		} catch (IOException e) {	// erreur lors de la connection
			//System.out.println("Erreur de connection");
			//e.printStackTrace(); // for DEBUG purposes
			this.isConnected = false;
			
			if(this.lostConn !=null)
				lostConn.lostConnection(e);
			
			return false;
		}
		
		// lancement du thread d'ecoute
		this.threadClient = new Thread(this);
		this.threadClient.start();
		
		return true;
	}
	
	/**
	 * Deconnection du client
	 */
	@SuppressWarnings("deprecation")
	public void disconnect(){
		if(socket!=null)
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace(); // for DEBUG purposes
			}
		// arret du thread
		this.threadClient.stop();
		this.isConnected = false;
	}
	/**
	 * Indique si le client est connecte
	 * @return si le client est connecte
	 */
	public boolean isConnected(){
		return isConnected;
	}
	/**
	 * Envoie des donnees sur le socket (seulement si le client est connecte, ne fait rien sinon).
	 * @param o les donnees a envoyer
	 */
	public void sendData(Object o) {
		if(this.isConnected){	// envoi seulement si le client est connecte
			try {
				if(this.out==null)
					this.out = new ObjectOutputStream(socket.getOutputStream());
				this.out.writeObject(o);
			} catch (IOException e) {
				if(this.lostConn !=null)
					this.lostConn.lostConnection(e);
			}
		}
	}

}
